/* Une animation en OpenGL                      */
/*                                              */
/* Auteurs: Teixeira Sophie - Ginhoux Alex       */
/* Septembre 2012                               */

#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <sstream>
#include <ctime>
#include <vector>
#include <cstring>
#include <cstdarg>

#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "StandardMonster.h"
#include "SuperMonster.h"
#include "Ship.h"

#define DURATION 1
#define GAUCHE -0.3F
#define DROITE 0.3F
#define INTER_Y 15
#define INTER_X 20

using namespace std;

/* Variables et constantes globales             */
/* pour les angles et les couleurs utilises     */

static float r3 = 0.0F;
static float r4 = 0.0F;

static int objet = 0;

static vector<Monster*> monsters;
static Ship *vaisseau;
static clock_t tic = clock();
static float direction_monstre = DROITE;

int h1 = 0;
int w1 = 0;
GLuint texture = 0;
static const char* backImg = "..\\Src\\img\\space_background.raw";

int shooter = 0;
int charger = 0;
int score = 0;
int _gameover = 0;

void bitmap_output(int x, int y, const char *string, void *font) {
	int len, i;
	glRasterPos2f(x, y);
	len = (int) strlen(string);
	for (i = 0; i < len; i++) {
		glutBitmapCharacter(font, string[i]); }
}

void background1() {
	glEnable(GL_TEXTURE_2D);
	// texture width/height
	const int iw = w1 + 200;
	const int ih = h1;

	glPushMatrix();
	glTranslatef( -iw/2, -ih/2, -100.0F );
	glBegin(GL_QUADS);
	glTexCoord2i(0,0); glVertex2i(0, 0);
	glTexCoord2i(1,0); glVertex2i(iw, 0);
	glTexCoord2i(1,1); glVertex2i(iw, ih);
	glTexCoord2i(0,1); glVertex2i(0, ih);
	glEnd();
	glPopMatrix();

	glDisable(GL_TEXTURE_2D);
}

void background2() {
	glEnable(GL_TEXTURE_2D);
	// texture width/height
	const int iw = w1 + 300;
	const int ih = h1;

	glPushMatrix();
	glRotatef(90.0, 1.0, 0.0, 0.0);
	glTranslatef(-iw/2, -ih/2, -100.0F);
	glBegin(GL_QUADS);
	glTexCoord2i(0,0); glVertex2i(0, 0);
	glTexCoord2i(1,0); glVertex2i(iw, 0);
	glTexCoord2i(1,1); glVertex2i(iw, ih);
	glTexCoord2i(0,1); glVertex2i(0, ih);
	glEnd();
	glPopMatrix();

	glDisable(GL_TEXTURE_2D);
}

/* Fonction d'initialisation des parametres     */
/* OpenGL ne changeant pas au cours de la vie   */
/* du programme                                 */
static void init(void) {
	const GLfloat shininess[] = { 50.0 };
	glMaterialfv(GL_FRONT, GL_SPECULAR, blanc);
	glMaterialfv(GL_FRONT, GL_SHININESS, shininess);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, rouge);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, jaune);
	glLightfv(GL_LIGHT2, GL_DIFFUSE, bleu);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHT2);
	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_NORMALIZE);
	glEnable(GL_AUTO_NORMAL);
}

static float camera = 0.0F;
/* Fonction executee lors d'un changement       */
/* de la taille de la fenetre OpenGL            */

static void reshape1(int x,int y) {
	h1 = x;
	w1 = y;
	glViewport(0,0,x,y); 
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	if ( x >= y )//25
		gluPerspective(35.0F,(float) x/y,200.0,900.0);
	else
		gluPerspective(30.0F*(float) y/x,(float) x/y,200.0,900.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0.0,0.0,500.0,
		0.0, 0.0, 0.0,
		0.0, 1.0, 0.0);
}

static void reshape2(int x,int y) {
	h1 = x;
	w1 = y;
	glViewport(0,0,x,y); 
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	if ( x >= y )//25
		gluPerspective(90.0F,(float) x/y,500.0,0.0);
	else
		gluPerspective(90.0F*(float) y/x,(float) x/y,500.0,0.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(vaisseau->coord->x, vaisseau->coord->y - 40.0, 27.0,
		vaisseau->coord->x, 200.0, 0.0,
		0.0, 0.0, 1.0);
}

static void gameover() {
	glClearColor(0.3F,0.3F,0.3F,0.5F);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	std::ostringstream stringscore;
	stringscore << "GAME OVER" << std::endl << "Score = " << score;
	std::string str =  stringscore.str();
	const char* chrscore = str.c_str();
	bitmap_output(0, 0, chrscore, GLUT_BITMAP_HELVETICA_18);
	glutPostRedisplay();


	std::cout << "GAME OVER - Score = " << score << std::endl;
	system("PAUSE");
	exit(0);
}

static void moveMonsters(){
	int max_width = -200000;
	int min_width = 200000;
	for (vector<Monster*>::iterator it = monsters.begin(); it != monsters.end(); ++it) {
		(*it)->move(direction_monstre, 0, 0);

		if ((*it)->isOnFire) {
			(*it)->tir->move(0.0F, -1.0F, 0.0F);
			(*it)->shootBox->move(0.0F, -1.0F, 0.0F);

			if (vaisseau->isShot((*it)->shootBox)) {
				(*it)->isOnFire = false;
				shooter--;
				std::cout << "Killed by shot" << std::endl;
				_gameover = 1;
				//gameover();
			}

			if((*it)->tir->y < -115){
				(*it)->isOnFire = false;
				(*it)->tir->setCoord3D((*it)->coord->x, (*it)->coord->y, (*it)->coord->z);
				(*it)->shootBox->setBox((*it)->tir->x, (*it)->tir->y, (*it)->tir->z, 0.0F, 0.0F, 0.0F);
				shooter--;
			}
		}


		if( (*it)->coord->x > max_width )
			max_width = (*it)->coord->x;
		else if( (*it)->coord->x < min_width )
			min_width = (*it)->coord->x;
	}

	if( max_width > 150 )
		direction_monstre = GAUCHE;
	if( min_width < -150 )
		direction_monstre = DROITE;

	for (vector<Monster*>::iterator it = monsters.begin(); it != monsters.end(); ++it) {
		if ((*it)->isCharger) {
			if (rand() < RAND_MAX/2) {
				(*it)->moveCharger(5*GAUCHE, -0.5F, 0);
			} else {
				(*it)->moveCharger(5*DROITE, -0.5F, 0);
			}

			if ((*it)->coordCharger->y <= -200) {
				(*it)->isCharger = false;
				charger--;
				(*it)->monsterBox->setBox((*it)->coord->x - 0.5F, (*it)->coord->y + 7.5F, (*it)->coord->z, (*it)->monsterBox->w, (*it)->monsterBox->h, (*it)->monsterBox->d);
			}


		}
	}

	for (vector<Monster*>::iterator it = monsters.begin(); it != monsters.end(); ++it) {
		if (vaisseau->shipBox->collision((*it)->monsterBox)) {
			std::cout << "Killed by collision" << std::endl;
			_gameover = 1;
			//gameover();
		}
	}
}

bool firstturn = true;
bool allDead = false;
static void idle(void){
	clock_t newT = clock();
	if( (double)(newT - tic) / 10  > DURATION && _gameover == 0){
		allDead = true;
		for (vector<Monster*>::iterator it = monsters.begin(); it != monsters.end(); ++it) {
			if ((*it)->isVisible) {
				allDead = false;
			}
		}

		if (allDead) {
			cout << "All monsters are dead" << endl;
			_gameover = 1;
		}

		tic = newT;
		moveMonsters();
		score += vaisseau->shoot(monsters);

		if (charger < 1) {
			int size = monsters.size();
			int index = rand() % size;
			Monster *m = monsters[index];
			if (m->isVisible && !m->isCharger && !m->isOnFire) {
				m->isCharger = true;
				m->coordCharger->setCoord3D(m->coord->x, m->coord->y, m->coord->z);
				charger++;
			}
		}

		if (shooter < 3) {
			int size = monsters.size();
			int index = rand() % size;
			Monster *m = monsters[index];

			if (m->isVisible && !m->isCharger && !m->isOnFire) {
				m->isOnFire = true;
				m->tir->setCoord3D(m->coord->x, m->coord->y, m->coord->z);
				m->shootBox->setBox(m->tir->x - 0.5F, m->tir->y - 1.0F, m->tir->z - 0.5F, 1.0F, 2.0F, 1.0F);
				shooter++;
			}
		}
	}

	glutPostRedisplay();
	firstturn = false;
}

/* Scene dessinee                              */
static void scene(void) {
	vaisseau->draw();
	for (vector<Monster*>::iterator it = monsters.begin(); it != monsters.end(); ++it) {
		(*it)->draw();
	}
}


/* Fonction executee lors d'un rafraichissement */
/* de la fenetre de dessin                      */
static void display1(void) {
	glClearColor(0.3F,0.3F,0.3F,0.5F);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (_gameover == 1) {
		glClearColor(0.3F,0.3F,0.3F,0.5F);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		std::ostringstream stringscore;
		stringscore << "GAME OVER - Score = " << score;
		std::string str =  stringscore.str();
		const char* chrscore = str.c_str();
		bitmap_output(-30, 0, chrscore, GLUT_BITMAP_HELVETICA_18);
	} else {
		std::ostringstream stringscore;
		stringscore << "Score = " << score;
		std::string str =  stringscore.str();
		const char* chrscore = str.c_str();
		bitmap_output(-140, 140, chrscore, GLUT_BITMAP_HELVETICA_18);
	}
	const GLfloat light0_position[] = { 0.0,0.0,0.0,1.0 };
	const GLfloat light1_position[] = { -1.0,1.0,1.0,0.0 };
	const GLfloat light2_position[] = { 1.0,-1.0,1.0,0.0 };
	glLightfv(GL_LIGHT0,GL_POSITION,light0_position);
	glLightfv(GL_LIGHT1,GL_POSITION,light1_position);
	glLightfv(GL_LIGHT2,GL_POSITION,light2_position);
	glPolygonMode(GL_FRONT_AND_BACK,GL_FILL );
	glPushMatrix();
	glTranslatef(0, 0, 0);

	scene();
	background1();

	glPopMatrix();
	glFlush();
	glutSwapBuffers();
	int error = glGetError();
	if ( error != GL_NO_ERROR )
		printf("Attention erreur %d\n",error);

}

static void display2(void) {
	glClearColor(0.3F,0.3F,0.3F,0.5F);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (_gameover == 1) {
		std::ostringstream stringscore;
		stringscore << "GAME OVER - Score = " << score;
		std::string str =  stringscore.str();
		const char* chrscore = str.c_str();
		bitmap_output(-50, 20, chrscore, GLUT_BITMAP_HELVETICA_18);
	} else {
		std::ostringstream stringscore;
		stringscore << "Score = " << score;
		std::string str =  stringscore.str();
		const char* chrscore = str.c_str();
		glPushMatrix();
		glTranslatef(-200.0F, 200.0F, 200.0F);
		bitmap_output(0, 0, chrscore, GLUT_BITMAP_HELVETICA_18);
		glPopMatrix();
	}
	const GLfloat light0_position[] = {0.0, 0.0, 0.0, 1.0};
	const GLfloat light1_position[] = {-1.0, 1.0, 1.0, 0.0};
	const GLfloat light2_position[] = {1.0, -1.0, 1.0, 0.0};
	glLightfv(GL_LIGHT0,GL_POSITION,light0_position);
	glLightfv(GL_LIGHT1,GL_POSITION,light1_position);
	glLightfv(GL_LIGHT2,GL_POSITION,light2_position);
	glPolygonMode(GL_FRONT_AND_BACK,GL_FILL );

	glPushMatrix();
	//glTranslatef(xPlayer, yPlayer, 0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(vaisseau->coord->x, vaisseau->coord->y - 40.0, 27.0,
		vaisseau->coord->x, 200.0, 0.0,
		0.0, 0.0, 1.0);
	scene();
	background2();

	glPopMatrix();

	glFlush();
	glutSwapBuffers();
	int error = glGetError();
	if ( error != GL_NO_ERROR )
		printf("Attention erreur %d\n",error);

}
/* Fonction executee lors de l'appui            */
/* d'une touche alphanumerique du clavier       */

static void keyboard(unsigned char key,int x,int y) {
	switch (key) {
	case 'p' :
		system("PAUSE");
		break;

	case ' ':
		vaisseau->fire();
		break;

	case 0x1B :
		exit(0);
		break;

	case 'Z' :
	case 'z' :
		if (vaisseau->coord->y < -50) {
			vaisseau->move(0.0F, 1.0F, 0.0F);
		}
		break;

	case 'S' :
	case 's' :
		if (vaisseau->coord->y > -100) {
			vaisseau->move(0.0F, -1.0F, 0.0F);
		}
		break;

	case 'Q' :
	case 'q' :
		if( vaisseau->coord->x > -150 )
			vaisseau->move(-1.0F, 0.0F, 0.0F);
		break;

	case 'D' :
	case 'd' :
		if( vaisseau->coord->x < 150 )
			vaisseau->move(1.0F, 0.0F, 0.0F);
		break;

	case 'R' :
	case 'r' :
		_gameover = 0;
		score = 0;
		charger = 0;
		shooter = 0;
		for (vector<Monster*>::iterator it = monsters.begin(); it != monsters.end(); ++it) {
			(*it)->init();
		}
		break;
	}
}


GLuint LoadTexture(const char *filename, int width, int height) { 
	// open texture data
	FILE *file = fopen(filename, "rb");
	if (file == NULL) return 0;

	// allocate buffer
	unsigned char *data = new unsigned char[width * height * 3];

	// read texture data
	fread(data, width * height * 3, 1, file);
	fclose(file);

	// allocate a texture name
	GLuint texture;
	glGenTextures(1, &texture);

	// select our current texture
	glBindTexture(GL_TEXTURE_2D, texture);

	// select modulate to mix texture with color for shading
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_DECAL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_DECAL);

	// when texture area is small, bilinear filter the closest mipmap
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	// when texture area is large, bilinear filter the first mipmap
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	// texture should tile
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

	// build our texture mipmaps
	gluBuild2DMipmaps(GL_TEXTURE_2D, 4, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);

	// free buffer
	delete [] data;

	return texture;
}


/* Fonction principale                          */

int main(int argc,char **argv) {
	vaisseau = new Ship( 0.0F, -100.0F, -20.0F);

	for (float y = 30; y <= 60; y += INTER_Y) {
		for (float x = -90; x <= 90; x += INTER_X) {
			monsters.push_back(new StandardMonster(x, y, -20.0F, 1));
		}
	}

	for (float x = -70; x <= 70; x += INTER_X) {
		monsters.push_back(new StandardMonster(x, 75, -20.0F, 2));
	}

	for (float x = -50; x <= 50; x += INTER_X) {
		monsters.push_back(new StandardMonster(x, 90, -20.0F, 3));
	}

	monsters.push_back(new SuperMonster(-30, 105, -20.0F));
	monsters.push_back(new SuperMonster(30, 105, -20.0F));

	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
	glutInitWindowSize(600,600);
	glutInitWindowPosition(30,100);
	glutCreateWindow("Galaxian 3D Vue globale");
	init();
	glutIdleFunc(idle);
	glutKeyboardFunc(keyboard);
	glutReshapeFunc(reshape1);
	glutDisplayFunc(display1);
	texture = LoadTexture(backImg, 1024, 768);

	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
	glutInitWindowSize(600,600);
	glutInitWindowPosition(650,100);
	glutCreateWindow("Galaxian 3D Vue Player");
	init();
	glutIdleFunc(idle);
	glutKeyboardFunc(keyboard);
	glutReshapeFunc(reshape2);
	glutDisplayFunc(display2);
	texture = LoadTexture(backImg, 1024, 768);

	glutMainLoop();

	monsters.clear();

	delete vaisseau;
	return(0);
}
